iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 12
1
AI & Data

D3.js資料視覺化的浪漫突進系列 第 12

Day12 D3js zoom 縮放事件

  • 分享至 

  • xImage
  •  

D3js d3.zoom

用途

針對滑鼠、鍵盤平移或水平縮放圖表,像是選取區間或是資料太密集需要縮放觀看時使用。

d3.zoom

建立zoom縮放事件。此API會替我們綁上監聽各種縮放事件。

// 當有縮放事件時,會發出zoom此事件。
selection.call(d3.zoom().on("zoom", zoomed));
// 可單純針對滾論滾動進行監聽。
selection
    .call(zoom)
    .on("wheel.zoom", null);

縮放事件列表

zoom針對各種事件細節列表。

Event Listening Element Zoom Event Default Prevented?
mousedown⁵ selection start no¹
mousemove² window¹ zoom yes
mouseup² window¹ end yes
dragstart² window - yes
selectstart² window - yes
click³ window - yes
dblclick selection multiple yes
wheel⁸ selection zoom⁷ yes
touchstart selection multiple no⁴
touchmove selection zoom yes
touchend selection end no⁴
touchcancel selection end no⁴

範例

先來製作基本圖表

const data = [
  {name: "E", value: 0.12702},
  {name: "T", value: 0.09056},
  {name: "A", value: 0.08167},
  {name: "O", value: 0.07507},
  {name: "I", value: 0.06966},
  {name: "N", value: 0.06749},
  {name: "S", value: 0.06327},
  {name: "H", value: 0.06094},
  {name: "R", value: 0.05987},
  {name: "D", value: 0.04253},
  {name: "L", value: 0.04025},
  {name: "C", value: 0.02782},
  {name: "U", value: 0.02758},
  {name: "M", value: 0.02406},
  {name: "W", value: 0.0236},
  {name: "F", value: 0.02288},
  {name: "G", value: 0.02015},
  {name: "Y", value: 0.01974},
  {name: "P", value: 0.01929},
  {name: "B", value: 0.01492},
  {name: "V", value: 0.00978},
  {name: "K", value: 0.00772},
  {name: "J", value: 0.00153},
  {name: "X", value: 0.0015},
  {name: "Q", value: 0.00095},
  {name: "Z", value: 0.00074},
  // ["letter", "frequency"]
]

const width = 800;
const height = 800;
const padding = {
  top: 20,
  right: 0,
  bottom: 30,
  left: 40
};

const svg = d3.select('svg').attr('width', width).attr('height', height);

const xScale = d3.scaleBand()
    .domain(data.map(d => d.name))
    .range([padding.left, width - padding.right])
    .padding(0.1)

const yScale = d3.scaleLinear()
    .domain([0, d3.max(data, d => d.value)]).nice()
    .range([height - padding.bottom, padding.top])

const xAxis = g => g
    .attr("transform", `translate(0,${height - padding.bottom})`)
    .call(d3.axisBottom(xScale).tickSizeOuter(0))

const yAxis = g => g
    .attr("transform", `translate(${padding.left},0)`)
    .call(d3.axisLeft(yScale))
    .call(g => g.select(".domain").remove())

svg.append("g")
      .attr("class", "bars")
      .attr("fill", "steelblue")
    .selectAll("rect")
    .data(data)
    .join("rect")
      .each(data => console.log(data))
      .attr("x", d => xScale(d.name))
      .attr("y", d => yScale(d.value))
      .attr("height", d => yScale(0) - yScale(d.value))
      .attr("width", xScale.bandwidth());

svg.append("g")
      .attr("class", "x-axis")
      .call(xAxis);

  svg.append("g")
      .attr("class", "y-axis")
      .call(yAxis);

zoom事件

function zoom(svg) {
  const extent = [[padding.left, padding.top], [width - padding.right, height - padding.top]];
  // 確定目前寬度最大最小範圍
 
  // 傳進來的svg綁上zoom事件
  svg.call(d3.zoom()
      .scaleExtent([1, 8])
      // zoom scaleExtent設置最大最小縮放比例
      .translateExtent(extent)
      // 可縮放的最大最小範圍
      .extent(extent)
      // 設定最大最小範圍
      .on("zoom", zoomed));
      // 當縮放事件發生時,對應的事件

  function zoomed(event) {
    xScale.range([padding.left, width - padding.right].map(d => event.transform.applyX(d)));
    // 重新計算xScale
    svg.selectAll(".bars rect").attr("x", d => xScale(d.name)).attr("width", xScale.bandwidth());
    // 並將所有的rect重新計算寬度以及位置
    svg.selectAll(".x-axis").call(xAxis);
    // 重新產生X軸比例尺
  }
}

圖表綁定zoom事件

透過svgcall來執行zoom的方法。

const data = [
  {name: "E", value: 0.12702},
  {name: "T", value: 0.09056},
  {name: "A", value: 0.08167},
  {name: "O", value: 0.07507},
  {name: "I", value: 0.06966},
  {name: "N", value: 0.06749},
  {name: "S", value: 0.06327},
  {name: "H", value: 0.06094},
  {name: "R", value: 0.05987},
  {name: "D", value: 0.04253},
  {name: "L", value: 0.04025},
  {name: "C", value: 0.02782},
  {name: "U", value: 0.02758},
  {name: "M", value: 0.02406},
  {name: "W", value: 0.0236},
  {name: "F", value: 0.02288},
  {name: "G", value: 0.02015},
  {name: "Y", value: 0.01974},
  {name: "P", value: 0.01929},
  {name: "B", value: 0.01492},
  {name: "V", value: 0.00978},
  {name: "K", value: 0.00772},
  {name: "J", value: 0.00153},
  {name: "X", value: 0.0015},
  {name: "Q", value: 0.00095},
  {name: "Z", value: 0.00074},
  // ["letter", "frequency"]
]

const width = 800;
const height = 800;
const padding = {
  top: 20,
  right: 0,
  bottom: 30,
  left: 40
};

// 關鍵在最後將執行zoom事件,會帶入此svg selection並執行zoom method。
const svg = d3.select('svg').attr('width', width).attr('height', height).call(zoom)

const xScale = d3.scaleBand()
    .domain(data.map(d => d.name))
    .range([padding.left, width - padding.right])
    .padding(0.1)

const yScale = d3.scaleLinear()
    .domain([0, d3.max(data, d => d.value)]).nice()
    .range([height - padding.bottom, padding.top])

const xAxis = g => g
    .attr("transform", `translate(0,${height - padding.bottom})`)
    .call(d3.axisBottom(xScale).tickSizeOuter(0))

const yAxis = g => g
    .attr("transform", `translate(${padding.left},0)`)
    .call(d3.axisLeft(yScale))
    .call(g => g.select(".domain").remove())

svg.append("g")
      .attr("class", "bars")
      .attr("fill", "steelblue")
    .selectAll("rect")
    .data(data)
    .join("rect")
      .each(data => console.log(data))
      .attr("x", d => xScale(d.name))
      .attr("y", d => yScale(d.value))
      .attr("height", d => yScale(0) - yScale(d.value))
      .attr("width", xScale.bandwidth());

svg.append("g")
      .attr("class", "x-axis")
      .call(xAxis);

  svg.append("g")
      .attr("class", "y-axis")
      .call(yAxis);


function zoom(svg) {
  const extent = [[padding.left, padding.top], [width - padding.right, height - padding.top]];
 
  svg.call(d3.zoom()
      .scaleExtent([1, 8])
      .translateExtent(extent)
      .extent(extent)
      .on("zoom", zoomed));

  function zoomed(event) {
    xScale.range([padding.left, width - padding.right].map(d => event.transform.applyX(d)));
    svg.selectAll(".bars rect").attr("x", d => xScale(d.name)).attr("width", xScale.bandwidth());
    svg.selectAll(".x-axis").call(xAxis);
  }
}

結論

zoom還有其他選取範圍或是垂直水平縮放,此範例僅使用水平縮放。

範例連結
Codepen

參考

D3-zoom


上一篇
Day11 D3js CSV呈現簡單COVID-19圖表
下一篇
Day13 D3js d3.invert反轉Scale取值
系列文
D3.js資料視覺化的浪漫突進30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言